/*
 * author      : 吴中勤
 * date        : 2018/9/15
 * description : 通用 MobyEventBody 输入校验
 *              为了防止扯皮, 在各个入口做校验
 *              更好的做法是, 放在各个 dto里面,自己校验. 如
 *              MobyOrderLine.checkIntegration()
 *              MobyTradeOrder.checkIntegration()...
 */

package com.deepexi.adapter.converter.sap;

import com.deepexi.adapter.common.StringUtil;
import com.deepexi.adapter.converter.sap.sapdict.SapDict;
import com.deepexi.adapter.converter.sap.sapdict.SapDictService;
import com.deepexi.adapter.converter.sap.sapdict.SapDictType;
import com.deepexi.adapter.facade.moby.request.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;

import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

@Service
public class MobyEventBodyCheck {

    private static final Logger logger = LoggerFactory.getLogger(MobyEventBodyCheck.class);

    @Autowired
    private SapDictService dictService;

    @Autowired
    private SapMobyMapService mapService;

    private ThreadLocal<StringBuilder> msgHolder = ThreadLocal.withInitial(() -> null);
    private ThreadLocal<String> topicValue = ThreadLocal.withInitial(() -> null);

    public boolean checkMobyEventBody(MobyEventBody body, StringBuilder sb) {
        return this.checkMobyEventBody(null, body, sb, false);
    }

    public boolean checkMobyEventBody(String topic, MobyEventBody body, StringBuilder sb, boolean isDeposit) {
        topicValue.set(topic);
        msgHolder.set(sb);

        boolean allOk = true;

        allOk = assertNotNull("MobyEventBody body 不能为空", body) && allOk;
        // 下订金时, 不用验证会员
        if (!isDeposit) {
            allOk = checkMember(body.getMobyMember()) && allOk;
        }
        // 出库
        allOk = checkStockOut(body.getStockOut()) && allOk;
        // 订单
        allOk = checkTradeOrder(body.getMobyTradeOrder(), isDeposit) && allOk;
        // 订单行
        allOk = checkOrderLineList(body.getFlatTradeOrderLineList()) && allOk;
        // 付款信息
        allOk = checkPaymentInfo(body.getMobyPayInfoList()) && allOk;
        // 退款信息
        allOk = checkRefund(body.getMobyRefundInfoList()) && allOk;

//        return allOk;
        return sb.length() == 0;
    }

    private boolean assertTrue(String msg, boolean b) {
        if (!b) {
            msgHolder.get().append('\n').append(msg);
            // logger.error(msg);
            // throw new RuntimeException(msg);
        }
        return b;
    }

    private boolean assertNotNull(String msg, Object o) {
        if (null == o) {
            msgHolder.get().append('\n').append(msg);
            // logger.error(msg);
            // throw new RuntimeException(msg);
        }
        return null != o;
    }

    private boolean checkRefund(List<MobyRefundInfo> refundList) {

        // 允许 refundList 为null
        if (null == refundList || 0 == refundList.size())
            return true;

        boolean allOk = true;
        // 必须要有 支付单号 退款单号 退款日期 退款操作人 退款状态
        for (MobyRefundInfo item : refundList) {
            allOk = assertTrue("退款检测 - 字段必须有值: tradeOrderPaymentId", isNotEmpty(item.getPayOrderNo())) && allOk;
            allOk = assertTrue("退款检测 - 字段必须有值: id", isNotEmpty(item.getRefundOrderId())) && allOk;
            allOk = assertTrue("退款检测 - 字段必须有值: updatedAt", isNotEmpty(item.getUpdatedAt())) && allOk;
            allOk = assertTrue("退款检测 - 字段必须有值: updatedBy", isNotEmpty(item.getOperatorId())) && allOk;
            allOk = assertTrue("退款检测 - 字段必须有值: refundMoney", isNotEmpty(item.getRefundMoney())) && allOk;
            allOk = assertTrue("退款检测 - 字段必须有值: status", isNotEmpty(item.getStatus())) && allOk;
        }
        return allOk;
    }

    private boolean checkPaymentInfo(List<MobyPayInfo> payList) {
        // moby事件中, 必须有支付信息
        boolean allOk = true;
        allOk = assertTrue("支付信息不能为存在", null != payList && 0 != payList.size()) && allOk;

        allOk = assertTrue("支付信息条数必须>0", payList.size() > 0) && allOk;
        for (MobyPayInfo item : payList) {
            allOk = assertTrue("支付检测 - 字段必须有值: tradeOrderId, tradeOrderId=" + item.getPayOrderNo(), isNotEmpty(item.getTradeOrderId())) && allOk;
            allOk = assertTrue("支付检测 - 字段必须有值: fundType, id=" + item.getPayOrderNo(), isNotEmpty(item.getFundType())) && allOk;
            allOk = assertTrue("支付检测 - 字段必须有值: payChannel, id=" + item.getPayOrderNo(), isNotEmpty(item.getPayChannel())) && allOk;
            allOk = assertTrue("支付检测 - 字段必须有值: payOrderNo, id=" + item.getPayOrderNo(), isNotEmpty(item.getPayOrderNo())) && allOk;
            allOk = assertTrue("支付检测 - 字段必须有值: payStatus, id=" + item.getPayOrderNo(), isNotEmpty(item.getPayStatus())) && allOk;
            allOk = assertTrue("支付检测 - 字段必须有值: paidAt, id=" + item.getPayOrderNo(), isNotEmpty(item.getPaidAt())) && allOk;
            allOk = assertTrue("支付检测 - 字段必须有值: payMoney, id=" + item.getPayOrderNo(), isNotEmpty(item.getPayMoney())) && allOk;
            allOk = assertNotNull("支付检测 - 字段必须存在: operatorId, id=" + item.getPayOrderNo(), item.getOperatorId()) && allOk;
            allOk = assertTrue("支付检测 - 字段必须有值: bankNo, id=" + item.getPayOrderNo(), isNotEmpty(item.getBankNo())) && allOk;

            // fundType 有效性判定
            allOk = moneyValid(item.getPayMoney()) && allOk;
            allOk = dateValid(item.getPaidAt()) && allOk;
            if (!StringUtil.isNullorEmpty(item.getFundType())) {
                allOk = assertTrue(
                        "支付检测 - 字段必须有效: fundType, fundType=" + item.getFundType(),
                        dictService.mobyValueCheck(SapDictType.FUND_TYPE, item.getFundType())) && allOk;
            }
            if (!StringUtil.isNullorEmpty(item.getFundType())) {
                allOk = assertTrue(
                        "支付检测 - 字段必须有效: payChannel, payChannel=" + item.getPayChannel(),
                        dictService.mobyValueCheck(SapDictType.PAY_WAY, item.getPayChannel())) && allOk;
            }
        }
        return allOk;
    }

    // 订单行检测入口
    private boolean checkOrderLineList(List<MobyOrderLine> lines) {
        boolean allOk = true;
        allOk = assertNotNull("订单行检测 - 不能为 null", lines) && allOk;
        if (null == lines || 0 == lines.size()) return false;

        // 字段校验
        allOk = assertTrue("订单行检测 - 不能为 长度=0", 0 != lines.size()) && allOk;

        // 物料编码可空
        for (MobyOrderLine line : lines) {
            if (StringUtil.isNullorEmpty(line.getMaterialCode()) && !mapService.isVehicleLine(line)) {
                logger.warn("非车辆订单行, 无物料, 忽略: id=" + line.getId() + ", productType=" + line.getProductType() + ",line=" + line.uglyString());
                continue;
            }

            if (mapService.isVehicleLine(line))
                allOk = checkLineVehicle(line) && allOk;
            else if (mapService.isInsuranceLine(line))
                allOk = checkLineInsurance(line) && allOk;
            else if (mapService.isLoanLine(line))
                allOk = checkLineLoan(line) && allOk;
            else if (mapService.isBoutiqueLine(line))
                allOk = checkLineBoutique(line) && allOk;
            else if (mapService.isServiceLine(line))
                allOk = checkLineService(line) && allOk;
            else
                // 其他
                logger.warn("不是车辆/保险/金融/精品/订单行, 忽略检测: id=" + line.getId() + ", productType=" + line.getProductType() + ",line=" + line.uglyString());
        }
        return allOk;
    }

    // 订单行: 通用检测
    private boolean checkCommonOrderLine(MobyOrderLine line) {
        boolean allOk = true;
        // 必须有 订单id 行id 物料编码 价钱 数量
        allOk = assertTrue("订单行检测 - 字段必须有值:订单行id", isNotBlank(line.getId())) && allOk;
        allOk = assertTrue("订单行检测 - 字段必须有值:订单id,id=" + line.getId(), isNotBlank(line.getTradeOrderId())) && allOk;
        if (!mapService.isVehicleLine(line)) {
            // 非车辆行必须有物料编码
            allOk = assertTrue("订单行检测 - 字段必须有值:物料编码,id=" + line.getId(), isNotBlank(line.getMaterialCode())) && allOk;
        }
        allOk = assertTrue("订单行检测 - 字段必须有值:数量,id=" + line.getId(), isNotBlank(line.getQuantity())) && allOk;
        allOk = assertTrue("订单行检测 - 字段必须有值:价格,id=" + line.getId(), isNotBlank(line.getPriceMoney())) && allOk;
        allOk = moneyValid(line.getPriceMoney()) && allOk;
        return allOk;
    }

    // 订单行: 车辆行 检验
    private boolean checkLineVehicle(MobyOrderLine line) {
        boolean allOk = true;
        allOk = checkCommonOrderLine(line) && allOk;
        // 必须有 车型编码
        allOk = assertTrue("订单行-车辆 - 字段必须有值: 车型编码", isNotBlank(line.getVehicleModelCode())) && allOk;
        allOk = assertTrue("订单行-车辆 - 字段必须有值: 车辆购置税金额", isNotBlank(line.getPurchaseTaxMoney())) && allOk;
        allOk = assertTrue("订单行-车辆 - 字段必须有值: 车辆购置税物料编码", isNotBlank(line.getPurchaseTaxMaterialCode())) && allOk;
        allOk = assertTrue("订单行-车辆 - 字段必须有值: 车辆购置税行id", isNotBlank(line.getPurchaseTaxLineId())) && allOk;
        // 可选 车辆编码
        return allOk;
    }

    // 订单行: 精品行 检验
    private boolean checkLineBoutique(MobyOrderLine line) {
        boolean allOk = true;
        allOk = checkCommonOrderLine(line) && allOk;
        // 必须有 库位
        allOk = assertTrue("订单行-精品 - 字段必须有值: 库位", isNotBlank(line.getWarehouseCode())) && allOk;
        // 字段必须存在并且有效: 自动采购
        allOk = autoPurchaseValid(line) && allOk;
        return allOk;
    }

    // 订单行: 保险 检验
    private boolean checkLineInsurance(MobyOrderLine line) {
        // 字段必须存在并且有效: 自动采购
        return autoPurchaseValid(line);
    }

    // 订单行: 分期 检验
    private boolean checkLineLoan(MobyOrderLine line) {
        if (line == null) return true;
        boolean allOk = true;
        // 必须有 首付额度 贷款额度
//        allOk = assertTrue("分期检验 - id=" + line.getId(), false) && allOk;
        allOk = assertTrue("分期检验 - 首付金额不正确," + isMoneyOk(line.getDownPayment()), !isNotBlank(isMoneyOk(line.getDownPayment()))) && allOk;
        allOk = assertTrue("分期检验 - 贷款金额不正确," + isMoneyOk(line.getLoanAmount()), !isNotBlank(isMoneyOk(line.getLoanAmount()))) && allOk;
        return allOk;
    }

    // 订单行: 服务 检验
    private boolean checkLineService(MobyOrderLine line) {
        return true;
    }

    private boolean checkTradeOrder(MobyTradeOrder t, boolean isDeposit) {
        boolean allOk = true;
        allOk = assertNotNull("节点必须存在: MobyTradeOrder", t) && allOk;
        if (null == t) return false;

        // 字段校验
        allOk = assertTrue("订单检测 - 字段必须有值: id", isNotBlank(t.getId())) && allOk;

        // 付订金不需要 销售顾问
        if (!isDeposit) {
            allOk = assertTrue("订单检测 - 字段必须有值: scCode", isNotBlank(t.getScCode())) && allOk;
        }
        allOk = assertTrue("订单检测 - 字段必须有值: shopCode", isNotBlank(t.getShopCode())) && allOk;
        allOk = assertTrue("订单检测 - 字段必须有值: memberId", isNotBlank(t.getMemberId())) && allOk;
        allOk = assertTrue("订单检测 - 字段必须有值: orderTypeCode", isNotBlank(t.getOrderTypeCode())) && allOk;
        allOk = assertTrue("订单检测 - 字段必须有值: payMoney", isNotBlank(t.getPayMoney())) && allOk;

        // orderTypeCode 检测
        String orderTypeCode = t.getOrderTypeCode();
        if (!StringUtil.isNullorEmpty(orderTypeCode)) {
            boolean orderTypeCodeValid = dictService.mobyValueCheck(SapDictType.ORDER_TYPE_SCENE, orderTypeCode);
            allOk = assertTrue("    orderTypeCode=" + orderTypeCode + ",不在字典表中", orderTypeCodeValid) && allOk;
            allOk = moneyValid(t.getPayMoney()) && allOk;
        }

        return allOk;
    }

    private String isMoneyOk(String moneyStr) {
        if (StringUtil.isNullorEmpty(moneyStr))
            return "价格不合法:null";

        try {
            BigDecimal payMoney = new BigDecimal(moneyStr);
            if (payMoney.compareTo(BigDecimal.ZERO) < 0) {
                return "价格数量不合法, 必须 >= 0.00";
            }
            return null;
        } catch (Exception ex) {
            return ("价格格式不合法:" + moneyStr);
        }
    }

    private boolean moneyValid(String moneyStr) {
        String errMsg = isMoneyOk(moneyStr);
        return assertTrue("    " + errMsg, StringUtil.isNullorEmpty(errMsg));
    }

    private boolean dateValid(String date) {
        boolean allOk = true;
        allOk = assertNotNull("    日期格式不合法,需要的格式=yyyy-MM-dd HH:mm:ss", DateFormatConvert.getDate(date)) && allOk;
        return allOk;
    }

    private boolean autoPurchaseValid(MobyOrderLine line) {
        boolean allOk = true;
        // 字段必须存在并且有效: 自动采购
        String autoPurchase = line.getAutoPurchase();
        allOk = assertNotNull("    字段不存在:自动采购标志, lineId=" + line.getId(), autoPurchase) && allOk;
        if (null != autoPurchase) {
            SapDict sapDict = dictService.getDictByMoby(SapDictType.AUTO_PURCHASE_FLAG, autoPurchase);
            allOk = assertNotNull("    字段无效:自动采购标志, lineId=" + line.getId() + ", autoPurchase=" + autoPurchase, sapDict) && allOk;
        }
        return allOk;
    }

    private boolean checkStockOut(StockOut s) {
        if (null == s) return true;

        boolean allOk = true;
        // 允许 stock字段为空 但是如果 节点存在,
        allOk = assertTrue("出库检测 - 字段必须有值: date", isNotBlank(s.getStockOutDate())) && allOk;
        List line = s.getMobyLineIdList();
        allOk = assertTrue("出库检测 - 字段必须有效: lines", null != line && 0 != line.size()) && allOk;
        return allOk;
    }

    private boolean checkMember(MobyMember m) {
        boolean allOk = true;
        allOk = assertNotNull("member 不能为null", m) && allOk;
        if (null == m) return false;

        allOk = assertTrue("会员检测 - 字段必须有值: id", isNotBlank(m.getId())) && allOk;
        allOk = assertTrue("会员检测 - 字段必须有值: name", isNotBlank(m.getName())) && allOk;
        allOk = assertTrue("会员检测 - 字段必须有值: gender", isNotBlank(m.getGender())) && allOk;
        allOk = assertTrue("会员检测 - 字段必须有值: idType", isNotBlank(m.getIdType())) && allOk;
        allOk = assertTrue("会员检测 - 字段必须有值: idNum", isNotBlank(m.getIdNum())) && allOk;
        allOk = assertTrue("会员检测 - 字段必须有值: addr", isNotBlank(m.getAddr())) && allOk;
        allOk = assertTrue("会员检测 - 字段必须有值: phone", isNotBlank(m.getPhone())) && allOk;

        // 证件类型
        if (StringUtil.isNullorEmpty(m.getIdType())) {
            allOk = assertTrue("会员检测 - 证件类型无效", dictService.mobyValueCheck(SapDictType.CREDENTIAL, m.getIdType())) && allOk;
        }
        if (StringUtil.isNullorEmpty(m.getGender())) {
            allOk = assertTrue("会员检测 - 性别无效", dictService.mobyValueCheck(SapDictType.GENDER, m.getGender())) && allOk;
        }
        return allOk;
    }
}
